﻿using com.ootii.Actors.AnimationControllers;
using com.ootii.Geometry;
using NpcFramework;
using System;
using UnityEngine;

namespace Gameplay.PVE
{
    public class LocomotionAgent : MonoBehaviour
    {

        /// <summary>
        /// The current state of the controller including speed, direction, etc.
        /// </summary>
        public MotionState State = new MotionState();
        public NpcSeeker seeker;
        public NpcData npcData;
        public int rotateSpeed = 180;
        /// <summary>
        /// Tracks the root motion so we can apply it later
        /// </summary>
        private Vector3 mRootMotionMovement = Vector3.zero;
        public Vector3 RootMotionMovement
        {
            get { return mRootMotionMovement; }
            set { mRootMotionMovement = value; }
        }

        /// <summary>
        /// Tracks the root motion rotation so we can apply it later
        /// </summary>
        private Quaternion mRootMotionRotation = Quaternion.identity;
        public Quaternion RootMotionRotation
        {
            get { return mRootMotionRotation; }
            set { mRootMotionRotation = value; }
        }
        /// <summary>
        /// Current movement caused by the motion. This should NOT be
        /// multiplied by delta-time to create displacement
        /// </summary>
        protected Vector3 mMovement = Vector3.zero;
        public Vector3 Movement
        {
            get { return mMovement; }
        }
        /// <summary>
        /// Amount of rotation (yaw) caused by the motion. This should NOT be
        /// multiplied by delta-time to create angular displacement. This is
        /// also considered a "local" rotation. So, it is relative to the current
        /// rotation.
        /// </summary>
        protected Quaternion mRotation = Quaternion.identity;
        public Quaternion Rotation
        {
            get { return mRotation; }
        }

        /// <summary>
        /// We use these classes to help smooth the input values so that
        /// movement doesn't drop from 1 to 0 immediately.
        /// </summary>
        protected FloatValue mInputX = new FloatValue(0f, 10);
        protected FloatValue mInputY = new FloatValue(0f, 10);
        protected FloatValue mInputMagnitude = new FloatValue(0f, 15);

        /// <summary>
        /// Fields to help smooth out the mouse rotation
        /// </summary>
        protected float mYaw = 0f;
        protected float mYawTarget = 0f;
        protected float mYawVelocity = 0f;

        private Animator _Animator;

        public Action lateUpdate;
        public bool forceUpdateRotation =false;
        public void RegistSeeker(NpcSeeker seeker,NpcData npcData)
        {
            this.seeker = seeker;
            this.npcData = npcData;
        }

        public void StetForceUpdateTargetRotation(Quaternion targetRotation)
        {
            mYawQuater = targetRotation;
            transform.parent.rotation = targetRotation;
            seeker.transform.rotation = targetRotation;
        }
        void Start()
        {
            _Animator = GetComponent<Animator>();
        }

        void FixedUpdate()
        {
            if (seeker == null)
            {
                return;
            }
            float lDeltaTime = Time.fixedDeltaTime;
            seeker.OnLateUpdate();
            ProcessUserInput();
            UpdateRootMotion(lDeltaTime, ref mRootMotionMovement, ref mRootMotionRotation);

            UpdateMotions(lDeltaTime);
            if (!forceUpdateRotation)
            {
                Rotate(mRootMotionRotation * mRotation * Quaternion.Euler(Vector3.zero * lDeltaTime));
            }
            Move(transform.parent.rotation * mRootMotionMovement);
            SetAnimatorProperties(ref State);
            if (seeker!=null)
            {
                seeker.transform.position = transform.parent.position;
                seeker.transform.rotation = transform.parent.rotation;
            }
        }

        private void SetAnimatorProperties(ref MotionState rState)
        {
            _Animator.SetFloat("WalkH", rState.InputX);
            _Animator.SetFloat("WalkV", rState.InputY);
            // The relative speed magnitude of the character (0 to 1)
            // Delay a bit before we update the speed if we're accelerating
            // or decelerating.
            //mMecanimUpdateDelay -= Time.deltaTime;
            //if (!rUseTrendData || mMecanimUpdateDelay <= 0f)
            //{
            //    _Animator.SetFloat("InputMagnitude", rState.InputMagnitudeTrend.Value); //, 0.05f, Time.deltaTime);
            //}
        }

        private void UpdateMotions(float rDeltaTime)
        {

            mMovement = Vector3.zero;
            mRotation = Quaternion.identity;
            // Smooth the input so we don't start and stop immediately in the blend tree.
            SmoothInput();

            RotateUsingInput(rotateSpeed, rDeltaTime, ref mRotation);

            
        }

        private void Move(Vector3 vector3)
        {
            transform.parent.position = new Vector3(transform.parent.position.x + vector3.x, transform.parent.position.y, transform.parent.position.z + vector3.z);
        }
        private Quaternion mYawQuater = Quaternion.identity;
        private void Rotate(Quaternion quaternion)
        {
            mYawQuater = mYawQuater * quaternion;
            var finalRotate = RotationTo(transform.parent.rotation, mYawQuater);
            transform.parent.rotation = transform.parent.rotation * finalRotate;
            npcData.Forward = transform.parent.forward;
        }

        /// <summary>
        /// Creates a quaternion that represents the rotation required to turn the
        /// original quaternion to the specified quaternion
        /// </summary>
        /// <param name="rFrom">This object we're operating on</param>
        /// <param name="rTo">Quaternion to rotate towards</param>
        /// <returns>Resulting quaternion</returns>
        public Quaternion RotationTo(Quaternion rFrom, Quaternion rTo)
        {
            return Quaternion.Inverse(rFrom) * rTo;
        }
        private void RotateUsingInput(int rSpeed, float rDeltaTime, ref Quaternion rRotation)
        {
            // If we don't have an input source, stop

            // Determine this frame's rotation
            float lYawDelta = 0f;
            float lYawSmoothing = 0.1f;

            if (seeker.IsViewingActivated)
            {
                lYawDelta = seeker.ViewX * rSpeed * rDeltaTime;
            }
            mYawTarget = mYawTarget + lYawDelta;
            // Smooth the rotation
            lYawDelta = (lYawSmoothing <= 0f ? mYawTarget : Mathf.SmoothDampAngle(mYaw, mYawTarget, ref mYawVelocity, lYawSmoothing)) - mYaw;
            mYaw = mYaw + lYawDelta;
            // Use this frame's smoothed rotation
            if (lYawDelta != 0f)
            {
                rRotation = Quaternion.Euler(0f, lYawDelta, 0f);
            }
        }

        private void SmoothInput()
        {
            MotionState lState = State;

            // Convert the input to radial so we deal with keyboard and gamepad input the same.
            float lInputMax = 0.5f;

            float lInputX = Mathf.Clamp(lState.InputX, -lInputMax, lInputMax);
            float lInputY = Mathf.Clamp(lState.InputY, -lInputMax, lInputMax);
            float lInputMagnitude = Mathf.Clamp(lState.InputMagnitudeTrend.Value, 0f, lInputMax);
            ConvertToRadialInput(ref lInputX, ref lInputY, ref lInputMagnitude);

            // Smooth the input
            mInputX.Add(lInputX);
            mInputY.Add(lInputY);
            mInputMagnitude.Add(lInputMagnitude);

            // Modify the input values to add some lag
            State.InputX = mInputX.Average;
            State.InputY = mInputY.Average;
            State.InputMagnitudeTrend.Replace(mInputMagnitude.Average);
        }

        private void UpdateRootMotion(float rDeltaTime, ref Vector3 rMovement, ref Quaternion rRotation)
        {
            //rRotation = Quaternion.identity;

            // Override root motion if we're meant to

            // Get rid of root-motion that is not aligned with our input
            float lMovementSpeed = rMovement.magnitude;

            rMovement.x = State.InputX;
            rMovement.y = 0f;
            rMovement.z = State.InputY;
            rMovement = rMovement.normalized * lMovementSpeed;
        }

        private void ProcessUserInput()
        {
            //if (_CameraTransform == null) { return; }

            // Grab the movement, but create a bit of a dead zone
            float lHInput = seeker.MovementX;
            float lVInput = seeker.MovementY;
            float lMagnitude = Mathf.Sqrt((lHInput * lHInput) + (lVInput * lVInput));

            // Add the value to our averages so we track trends. 
            State.InputMagnitudeTrend.Value = lMagnitude;

            // Get out early if we can simply this
            if (lVInput == 0f && lHInput == 0f)
            {
                State.InputX = 0f;
                State.InputY = 0f;
                State.InputForward = Vector3.zero;
                State.InputFromAvatarAngle = 0f;
                State.InputFromCameraAngle = 0f;

                seeker.InputFromCameraAngle = float.NaN;
                seeker.InputFromAvatarAngle = float.NaN;

                return;
            }

            // Set the forward direction of the input
            State.InputX = lHInput;
            State.InputY = lVInput;
            State.InputForward = new Vector3(lHInput, 0f, lVInput);
        }

        void OnAnimatorMove()
        {
            if (Time.deltaTime == 0f)
            {
                mRootMotionMovement = Vector3.zero;
                mRootMotionRotation = Quaternion.identity;

                return;
            }

            //Log.FileWrite("OnAnimatorMove");

            // Clear any root motion values
            if (_Animator == null)
            {
                mRootMotionMovement = Vector3.zero;
                mRootMotionRotation = Quaternion.identity;
            }
            // Store the root motion as relative to the forward direction.
            else
            {
                // Convert the movement to relative the current rotation
                mRootMotionMovement = Quaternion.Inverse(transform.parent.rotation) * (_Animator.deltaPosition);

                // We don't want delta time spikes to cause our character to move erratically. So, instead we
                // translate the movement using our smoothed delta time.

                // Store the rotation as a velocity per second.
                mRootMotionRotation = _Animator.deltaRotation;
            }
        }
        /// <summary>
        /// The keyboard provides linear x,y input while the Xbox controller
        /// provides radial x,y input. This function convers from linear to radial.
        /// 
        /// 1.0, 1.0 (mag 1.4) becomes 0.7, 0.7 (mag 1.0)
        /// 1.0, 0.0 (mag 1.0) stays   1.0, 0.0 (mag 1.0)
        /// </summary>
        /// <param name="rInputX"></param>
        /// <param name="rInputY"></param>
        /// <param name="rMagnitude"></param>
        private void ConvertToRadialInput(ref float rInputX, ref float rInputY, ref float rMagnitude, float rMultiplier = 1f)
        {
            if (rMagnitude > 1f) { rMagnitude = 1f; }

            float lAngle = Mathf.Atan2(rInputX, rInputY) - 1.5708f;

            rInputX = rMagnitude * Mathf.Cos(lAngle);
            rInputY = rMagnitude * -Mathf.Sin(lAngle);
            rMagnitude = Mathf.Sqrt((rInputX * rInputX) + (rInputY * rInputY));

            if (rMultiplier != 1f)
            {
                rInputX = rInputX * rMultiplier;
                rInputY = rInputY * rMultiplier;
                rMagnitude = rMagnitude * rMultiplier;
            }
        }
        private void OnDestroy()
        {
            seeker = null;
            npcData = null;
        }
    }
}
